package fr.adrienbrault.idea.symfony2plugin.tests.templating;

import com.intellij.patterns.PlatformPatterns;
import com.jetbrains.php.lang.psi.elements.Field;
import com.jetbrains.php.lang.psi.elements.Function;
import com.jetbrains.php.lang.psi.elements.Method;
import com.jetbrains.php.lang.psi.elements.PhpClass;
import com.jetbrains.twig.TwigFileType;
import com.jetbrains.twig.elements.TwigBlockTag;
import fr.adrienbrault.idea.symfony2plugin.templating.TwigPattern;
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil;
import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase;

import java.util.regex.Pattern;

/**
 * @author Daniel Espendiller <daniel@espendiller.net>
 * @see fr.adrienbrault.idea.symfony2plugin.templating.TwigTemplateGoToDeclarationHandler
 */
public class TwigTemplateGoToDeclarationHandlerTest extends SymfonyLightCodeInsightFixtureTestCase {
    public void setUp() throws Exception {
        super.setUp();
        myFixture.copyFileToProject("classes.php");
        myFixture.copyFileToProject("TwigTemplateGoToLocalDeclarationHandler.php");
    }

    public String getTestDataPath() {
        return "src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/fixtures";
    }

    /**
     * @see fr.adrienbrault.idea.symfony2plugin.templating.TwigTemplateGoToDeclarationHandler
     */
    public void testSimpleTestNavigationToExtension() {
        myFixture.copyFileToProject("TwigFilterExtension.php");

        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{% if foo is bar<caret>_even %}",
            PlatformPatterns.psiElement(Function.class).withName("twig_test_even")
        );

        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{% if foo is bar ev<caret>en %}",
            PlatformPatterns.psiElement(Function.class).withName("twig_test_even")
        );

        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{% if foo is b<caret>ar even %}",
            PlatformPatterns.psiElement(Function.class).withName("twig_test_even")
        );

        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{% if foo is not bar<caret>_even %}",
            PlatformPatterns.psiElement(Function.class).withName("twig_test_even")
        );

        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{% if foo is not bar ev<caret>en %}",
            PlatformPatterns.psiElement(Function.class).withName("twig_test_even")
        );
    }

    public void testGetVarClassGoto() {
        assertNavigationMatch(TwigFileType.INSTANCE, "{# @var bar \\Date<caret>Time #}", PlatformPatterns.psiElement(PhpClass.class));
        assertNavigationMatch(TwigFileType.INSTANCE, "{# @var \\Date<caret>Time bar #}", PlatformPatterns.psiElement(PhpClass.class));

        assertNavigationMatch(TwigFileType.INSTANCE, "{# @var \\Date<caret>Time[] bar #}", PlatformPatterns.psiElement(PhpClass.class));
        assertNavigationMatch(TwigFileType.INSTANCE, "{# @var bar \\Date<caret>Time[] #}", PlatformPatterns.psiElement(PhpClass.class));
    }

    public void testGetVarClassGotoDeprecated() {
        assertNavigationMatch(TwigFileType.INSTANCE, "{# bar \\Date<caret>Time #}", PlatformPatterns.psiElement(PhpClass.class));
        assertNavigationMatch(TwigFileType.INSTANCE, "{# bar \\Date<caret>Time[] #}", PlatformPatterns.psiElement(PhpClass.class));
    }

    public void testSeeTagGoto() {
        assertNavigationMatch(TwigFileType.INSTANCE, "{# @see \\Date<caret>Time #}", PlatformPatterns.psiElement(PhpClass.class));
        assertNavigationMatch(TwigFileType.INSTANCE, "{# @see Date<caret>Time #}", PlatformPatterns.psiElement(PhpClass.class));
        assertNavigationMatch(TwigFileType.INSTANCE, "{# @see Date<caret>Time::format #}", PlatformPatterns.psiElement(Method.class));
        assertNavigationMatch(TwigFileType.INSTANCE, "{# @see \\Date<caret>Time:format #}", PlatformPatterns.psiElement(Method.class));
        assertNavigationMatch(TwigFileType.INSTANCE, "{# \\Date<caret>Time:format #}", PlatformPatterns.psiElement(Method.class));
    }

    public void testSeeTagGotoRegexMatch() {
        for (String s : new String[]{"\\DateTime", "\\DateTime::format", "foo/foo.html.twig", "@foo/foo.html.twig", "@foo/fo-o.html.twig", "@foo\\fo-o.html.twig", "Include/type_embed.html.twig"}) {
            assertTrue(Pattern.compile(TwigPattern.DOC_SEE_REGEX).matcher("{# @see " + s + " #}").find());
            assertTrue(Pattern.compile(TwigPattern.DOC_SEE_REGEX).matcher("{# @see " + s + "#}").find());

            assertTrue(Pattern.compile(TwigUtil.DOC_SEE_REGEX_WITHOUT_SEE).matcher("{# " + s + " #}").find());
            assertTrue(Pattern.compile(TwigUtil.DOC_SEE_REGEX_WITHOUT_SEE).matcher("{# " + s + "#}").find());
        }
    }

    public void testThatConstantAndEnumProvidesNavigation() {
        assertNavigationMatch(TwigFileType.INSTANCE, "{{ constant('\\Foo\\ConstantBar\\Foo::F<caret>OO') }}", PlatformPatterns.psiElement(Field.class).withName("FOO"));
        assertNavigationMatch(TwigFileType.INSTANCE, "{{ constant('\\\\Foo\\\\ConstantBar\\\\Foo::F<caret>OO') }}", PlatformPatterns.psiElement(Field.class).withName("FOO"));

        assertNavigationMatch(TwigFileType.INSTANCE, "{% if foo == constant('\\Foo\\ConstantBar\\Foo::F<caret>OO') %}", PlatformPatterns.psiElement(Field.class).withName("FOO"));
        assertNavigationMatch(TwigFileType.INSTANCE, "{% set foo == constant('\\Foo\\ConstantBar\\Foo::F<caret>OO') %}", PlatformPatterns.psiElement(Field.class).withName("FOO"));

        assertNavigationMatch(TwigFileType.INSTANCE, "{{ constant('\\\\App\\\\FooEnum::F<caret>OO') }}", PlatformPatterns.psiElement());
    }

    public void testTestControllerActionsProvidesReferences() {
        assertNavigationMatch(TwigFileType.INSTANCE, "{{ controller('\\FooBundle\\Cont<caret>roller\\FooController::barAction') }}", PlatformPatterns.psiElement(Method.class).withName("barAction"));
        assertNavigationMatch(TwigFileType.INSTANCE, "{{ controller('\\\\FooBundle\\\\Cont<caret>roller\\\\FooController::barAction') }}", PlatformPatterns.psiElement(Method.class).withName("barAction"));
        assertNavigationMatch(TwigFileType.INSTANCE, "{{ controller('FooBundle\\Cont<caret>roller\\FooController::barAction') }}", PlatformPatterns.psiElement(Method.class).withName("barAction"));
        assertNavigationMatch(TwigFileType.INSTANCE, "{{ controller('FooBundle\\\\Cont<caret>roller\\\\FooController::barAction') }}", PlatformPatterns.psiElement(Method.class).withName("barAction"));
    }

    public void testFunctionNavigation() {
        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{{ foo<caret>_test() }}", PlatformPatterns.psiElement(Function.class).withName("foo_test")
        );

        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{{ foo<caret>_test }}", PlatformPatterns.psiElement(Function.class).withName("foo_test")
        );

        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{% set foo = foo<caret>_test %}", PlatformPatterns.psiElement(Function.class).withName("foo_test")
        );

        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{% set foo = foo<caret>_test() %}", PlatformPatterns.psiElement(Function.class).withName("foo_test")
        );

        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{% if foo<caret>_test() %}{% endif %}", PlatformPatterns.psiElement(Function.class).withName("foo_test")
        );

        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{% if %}{% elseif foo<caret>_test() %}{% endif %}", PlatformPatterns.psiElement(Function.class).withName("foo_test")
        );

        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{% for user in foo<caret>_test() %}{% endfor %}", PlatformPatterns.psiElement(Function.class).withName("foo_test")
        );

        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{% for user in users if foo<caret>_test() %}{% endfor %}", PlatformPatterns.psiElement(Function.class).withName("foo_test")
        );
    }

    public void testTokenTagNavigation() {
        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{% tag_<caret>foobar 'foo' %}", PlatformPatterns.psiElement()
        );

        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{% tag_foobar 'foo' %}{% endtag_<caret>foobar %}", PlatformPatterns.psiElement()
        );
    }

    public void testMacroImport() {
        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{% import _self as macros %}\n" +
                "{% macro renderItem(form) %}{% endmacro %}\n" +
                "\n" +
                "{{ macros.rend<caret>erItem(foo) }}" +
                "",
            PlatformPatterns.psiElement()
        );

        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{% import _self as macros %}\n" +
                "{% macro renderItem(form) %}{% endmacro %}\n" +
                "\n" +
                "{{ macros.rend<caret>erItem() }}" +
                "",
            PlatformPatterns.psiElement()
        );

        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{% import _self as macros %}\n" +
                "{% macro renderItem(form) %}{% endmacro %}\n" +
                "\n" +
                "{{ macros.rend<caret>erItem }}" +
                "",
            PlatformPatterns.psiElement()
        );
    }

    public void testSelfMacroImport() {
        assertNavigationMatch(
            TwigFileType.INSTANCE,
                "{% macro foobar(name) %}{% endmacro %}\n" +
                "{{ _self.foo<caret>bar('password') }}",
            PlatformPatterns.psiElement()
        );

        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{% macro foobar(name) %}{% endmacro %}\n" +
                "{{ _self.foo<caret>bar }}",
            PlatformPatterns.psiElement()
        );

        assertNavigationIsEmpty(
            TwigFileType.INSTANCE,
                "{% macro foobarUnknown(name) %}\n" +
                "{% endmacro %}\n" +
                "{{ _self.foo<caret>bar('password') }}"
        );
    }

    public void testComponentNameNavigation() {
        assertNavigationMatch(
            TwigFileType.INSTANCE,
                "{{ component('Ale<caret>rt') }}",
            PlatformPatterns.psiElement(PhpClass.class)
        );
    }

    public void testComponentNameTagNavigation() {
        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{% component Ale<caret>rt %}",
            PlatformPatterns.psiElement(PhpClass.class)
        );
    }

    public void testFoo() {
        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{{ path('App\\\\Controller\\\\Foobar<caret>Controller') }}",
            PlatformPatterns.psiElement(Method.class)
        );

        assertNavigationMatch(
            TwigFileType.INSTANCE,
            "{{ path('App\\\\Controller\\\\FooCo<caret>ntroller::foobar') }}",
            PlatformPatterns.psiElement(Method.class)
        );
    }
}
